﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;

namespace ImageOCR
{
    /// <summary>
    /// 影像药品OCR识别类
    /// </summary>
    public class ImageMedicineOCR
    {
        private static int W { get; set; }

        private static int H { get; set; }

        /// <summary>
        /// OCR识别药品名称
        /// </summary>
        /// <param name="imageData">影像数据</param>
        /// <returns>药品名称与对应位置列表</returns>
        public static List<Medicine> GetMedicineList(byte[] imageData)
        {
            var ms = new MemoryStream(imageData);
            var bm = (Bitmap)Image.FromStream(ms);
            ms.Close();
            return GetMedicineList(bm);
        }

        /// <summary>
        /// OCR识别药品名称
        /// </summary>
        /// <param name="bm">影像</param>
        /// <returns>药品名称与对应位置列表</returns>
        public static List<Medicine> GetMedicineList(Bitmap bm)
        {
            var ml = new List<Medicine>();
            W = bm.Width;
            H = bm.Height;

            //*************************************************
            //* 第一步：影像处理
            //* 处理并分析影像，得到精确的药品区域
            //*************************************************

            //纠偏
            var newBitmap = ImageOprationControl.ImageRectification(bm);
            var im = ImageOprationControl.ToBinary(newBitmap, ImageOprationControl.GetThreshold(newBitmap));

            //找横线
            var hLines = ImageOprationControl.FindHorizontalLines(im, ConfigOpration.LineSpace, W / ConfigOpration.HLineMinLengthRatio, W);

            //找竖线
            var vLines = ImageOprationControl.FindVerticalLines(im, ConfigOpration.LineSpace, H / ConfigOpration.VLineMinLengthRatio, H);

            //去噪音线
            hLines = ImageOprationControl.DeleteNoiseLines(hLines, vLines, ConfigOpration.NoiseHLinesLimit, W / ConfigOpration.IsolateRatio);
            vLines = ImageOprationControl.DeleteNoiseLines(vLines, hLines, ConfigOpration.NoiseVLinesLimit, H / ConfigOpration.IsolateRatio);

            //去边界线
            ImageOprationControl.DeleteEdgeLines(ref hLines, ref vLines, W, H);
            //直线修正
            ImageOprationControl.CorrectLines(ref vLines, ref hLines, ConfigOpration.CorrectLinesLimit);

            //根据直线分析影像类型，并返回药品区域
            var contentRectangle = new Rectangle(0, 0, W, H);
            var imageType = ImageOprationControl.AnalysicImageClass(hLines, vLines, W, H, ref contentRectangle);

            //*************************************************
            //* 第二步：找药品区域
            //* 根据不同的影像类型分别进行处理，得到药品大概区域
            //*************************************************

            //根据影像类型确定药品区域
            if (imageType == ImageType.Bill)
            {
                ml = shangHaiBillImageOpration(newBitmap, W, H, contentRectangle);
            }
            else if (imageType == ImageType.TableDetail)
            {
                ml = tableDetailImageOpration(newBitmap, contentRectangle, hLines, vLines);
            }
            else if (imageType == ImageType.FrameDetail)
            {
                ml = frameDetailImageOpration(newBitmap, W, H, contentRectangle);
            }
            else if (imageType == ImageType.NormalDetail)
            {
                ml = normalDetailImageOpration(newBitmap, W, H, contentRectangle);
            }

            //清空文件夹
            FilesClean.Clean(ConfigOpration.SavePath);
            return ml;
        }

        #region 辅助方法

        /// <summary>
        /// 上海发票类型影像处理方法
        /// </summary>
        /// <param name="bm">二值影像</param>
        /// <param name="w">影像宽</param>
        /// <param name="h">影像高</param>
        /// <param name="contentRec">矩形药品区域</param>
        /// <returns>识别后的药品列表</returns>
        private static List<Medicine> shangHaiBillImageOpration(Bitmap bm, int w, int h, Rectangle contentRec)
        {
            var ml = new List<Medicine>();

            var obm = getMedicinesAreaBitmap(bm, contentRec, w, h);

            //根据影像类型分析药品区域，确定单个药品的位置，截图并保存
            var listRectangles = getEachMedicinesArea(obm, contentRec, ConfigOpration.RightOffset, ConfigOpration.ShangHaiBillLineHeight);
            foreach (var rectangles in listRectangles)
            {
                var folderName = DateTime.Now.ToString("yyyyMMddHHmmssffff");
                var _path = ConfigOpration.SavePath + folderName + "\\" + listRectangles.IndexOf(rectangles) + "\\";

                if (!Directory.Exists(_path))
                {
                    Directory.CreateDirectory(_path);
                }
                //将每个矩形区域保存为 tif 格式的图片
                ImageOprationControl.SaveImageToTif(new Bitmap(obm), rectangles, _path);

                for (int i = 1; i <= rectangles.Count; i++)
                {
                    var _pathImg = _path + i + ".tif";
                    var s = "";
                    if (OCR.ImageToOCR(_pathImg, ref s))
                    {
                        var allMedicines = new MatchMedicine();
                        string matchtMedicine = string.Empty;
                        if (MatchMedicine.IsRefeshData)
                        {
                            MatchMedicine.AllMedicinesFromDataBase = allMedicines.SelectMedicine();
                            MatchMedicine.IsRefeshData = false;
                        }
                        //获取与字符串符合的药品
                        var hasMatch = MatchMedicine.SimilarMatching(s, ref matchtMedicine, ConfigOpration.RaForEncoding);

                        if (hasMatch)
                        {
                            var area = (rectangles[i - 1].X + contentRec.X) + "," + (rectangles[i - 1].Y + contentRec.Y) + "," +
                                   rectangles[i - 1].Width + "," + rectangles[i - 1].Height;
                            var m = new Medicine { Area = area, MedicineName = matchtMedicine };
                            ml.Add(m);
                        }
                    }
                }
            }

            return ml;
        }

        /// <summary>
        /// 表格类型影像处理方法
        /// </summary>
        /// <param name="bm">二值影像</param>
        /// <param name="contentRec">矩形药品区域</param>
        /// <param name="hLines">横线集合</param>
        /// <param name="vLines">竖线集合</param>
        /// <returns>识别后的药品列表</returns>
        private static List<Medicine> tableDetailImageOpration(Bitmap bm, Rectangle contentRec, List<Line> hLines, List<Line> vLines)
        {
            var ml = new List<Medicine>();

            //清除超出范围的直线
            for (int i = 0; i < hLines.Count; i++)
            {
                if (hLines[i].A < contentRec.Y || hLines[i].A > contentRec.Y + contentRec.Height)
                {
                    hLines.Remove(hLines[i]);
                    i--;
                }
            }

            for (int i = 0; i < vLines.Count; i++)
            {
                if (vLines[i].A < contentRec.X || vLines[i].A > contentRec.X + contentRec.Width)
                {
                    vLines.Remove(vLines[i]);
                    i--;
                }
            }

            //找小矩形区域
            var recList = new List<Rectangle>();
            var arrH = new List<int[]>();
            var arrV = new List<int[]>();
            var intses = new int[2];
            intses[0] = contentRec.Y;
            for (int j = intses[0] + 8; j < contentRec.Y + contentRec.Height; j++)
            {
                var findHLines = hLines.FindAll(l => l.A == j);
                if (findHLines.Any())
                {
                    if (j - intses[0] < 18)
                    {
                        continue;
                    }
                    intses[1] = j;
                    var temp = new int[2];
                    temp[0] = intses[0];
                    temp[1] = intses[1];
                    arrV.Add(temp);
                    intses[0] = j;
                    intses[1] = 0;
                }
            }

            intses[0] = contentRec.X;
            intses[1] = 0;
            for (int i = intses[0] + 8; i < contentRec.X + contentRec.Width; i++)
            {
                var findVLines = vLines.FindAll(l => l.A == i);
                if (findVLines.Any())
                {
                    if (i - intses[0] < 18)
                    {
                        continue;
                    }
                    intses[1] = i;
                    var temp = new int[2];
                    temp[0] = intses[0];
                    temp[1] = intses[1];
                    arrH.Add(temp);
                    intses[0] = i;
                    intses[1] = 0;
                }
            }

            //分析列
            var maxColumn = arrH[0][1] - arrH[0][0];
            var maxInArrH = arrH[0];
            foreach (var intse in arrH)
            {
                if (maxColumn < intse[1] - intse[0])
                {
                    maxColumn = intse[1] - intse[0];
                    maxInArrH = intse;
                }
            }

            var newArrH = new List<int[]> { maxInArrH };
            var likenessColumns = arrH.FindAll(i => i != maxInArrH && (Math.Abs(i[1] - i[0] - maxColumn) < 10));
            if (likenessColumns.Any())
            {
                newArrH.AddRange(likenessColumns);
            }

            foreach (var intseV in arrV)
            {
                foreach (var intseH in newArrH)
                {
                    var rec = new Rectangle(intseH[0], intseV[0], intseH[1] - intseH[0], intseV[1] - intseV[0]);
                    recList.Add(rec);
                }
            }

            //根据影像类型分析药品区域，确定单个药品的位置，截图并保存
            var folderName = DateTime.Now.ToString("yyyyMMddHHmmssffff");
            var path = ConfigOpration.SavePath + folderName + "\\";
            //将每个矩形区域保存为 tif 格式的图片
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            ImageOprationControl.SaveImageToTif(new Bitmap(bm), recList, path);

            for (int i = 1; i <= recList.Count; i++)
            {
                var _pathImg = path + i + ".tif";
                var s = "";
                if (OCR.ImageToOCR(_pathImg, ref s))
                {
                    var allMedicines = new MatchMedicine();
                    string matchtMedicine = string.Empty;
                    if (MatchMedicine.IsRefeshData)
                    {
                        MatchMedicine.AllMedicinesFromDataBase = allMedicines.SelectMedicine();
                        MatchMedicine.IsRefeshData = false;
                    }
                    //获取与字符串符合的药品
                    var hasMatch = MatchMedicine.SimilarMatching(s, ref matchtMedicine, ConfigOpration.RaForOnlyName);

                    if (hasMatch)
                    {
                        var area = (recList[i - 1].X + contentRec.X) + "," + (recList[i - 1].Y + contentRec.Y) + "," +
                                   recList[i - 1].Width + "," + recList[i - 1].Height;
                        var m = new Medicine { Area = area, MedicineName = matchtMedicine };
                        ml.Add(m);
                    }
                }
            }

            return ml;
        }

        /// <summary>
        /// 带框类型影像处理方法
        /// </summary>
        /// <param name="bm">二值影像</param>
        /// <param name="w">影像宽</param>
        /// <param name="h">影像高</param>
        /// <param name="contentRec">矩形药品区域</param>
        /// <returns>识别后的药品列表</returns>
        private static List<Medicine> frameDetailImageOpration(Bitmap bm, int w, int h, Rectangle contentRec)
        {
            var ml = new List<Medicine>();

            var obm = getMedicinesAreaBitmap(bm, contentRec, w, h);
            var gs = ImageOprationControl.ToBinary(obm, ImageOprationControl.GetThreshold(obm));

            //针对针式打印扫描后的影像作闭合处理
            //gs = ImageOprationControl.Dilate(obm, gs, 1);
            //gs = ImageOprationControl.Erode(gs, contentRec.Width, contentRec.Height, 1);

            var pgs = ImageOprationControl.HorizontalProjection(gs);
            obm = ImageOprationControl.ToBitmap(gs);
            var listRectangleRow = ImageOprationControl.GetRowArea(pgs, contentRec.Width, contentRec.Height, ConfigOpration.RightOffset, ConfigOpration.FrameDetailLineHeight);
            var listRectangles = ImageOprationControl.AnalysisColumn2(gs, listRectangleRow);

            foreach (var rectangles in listRectangles)
            {
                var folderName = DateTime.Now.ToString("yyyyMMddHHmmssffff");
                var _path = ConfigOpration.SavePath + folderName + "\\" + listRectangles.IndexOf(rectangles) + "\\";

                //将每个矩形区域保存为 tif 格式的图片
                if (!Directory.Exists(_path))
                {
                    Directory.CreateDirectory(_path);
                }
                ImageOprationControl.SaveImageToTif(new Bitmap(obm), rectangles, _path);

                for (int i = 1; i <= rectangles.Count; i++)
                {
                    var _pathImg = _path + i + ".tif";
                    var s = "";
                    if (OCR.ImageToOCR(_pathImg, ref s))
                    {
                        var allMedicines = new MatchMedicine();
                        string matchtMedicine = string.Empty;
                        if (MatchMedicine.IsRefeshData)
                        {
                            MatchMedicine.AllMedicinesFromDataBase = allMedicines.SelectMedicine();
                            MatchMedicine.IsRefeshData = false;
                        }
                        //获取与字符串符合的药品
                        var hasMatch = MatchMedicine.SimilarMatching(s, ref matchtMedicine, ConfigOpration.RaForOnlyName);

                        if (hasMatch)
                        {
                            var area = (rectangles[i - 1].X + contentRec.X) + "," + (rectangles[i - 1].Y + contentRec.Y) + "," +
                                   rectangles[i - 1].Width + "," + rectangles[i - 1].Height;
                            var m = new Medicine { Area = area, MedicineName = matchtMedicine };
                            ml.Add(m);
                        }
                    }
                }
            }

            return ml;
        }

        /// <summary>
        /// 普通类型影像处理方法
        /// </summary>
        /// <param name="bm">二值影像</param>
        /// <param name="w">影像宽</param>
        /// <param name="h">影像高</param>
        /// <param name="contentRec">矩形药品区域</param>
        /// <returns>识别后的药品列表</returns>
        private static List<Medicine> normalDetailImageOpration(Bitmap bm, int w, int h, Rectangle contentRec)
        {
            var ml = new List<Medicine>();

            //截取影像内容区
            var bms = getMedicinesAreaBitmap(bm, contentRec, w, h);
            var binaryBm = new Bitmap(bms);

            //横向二值采样（采样率 = 宽度/80，约等于32）
            bms = ImageOprationControl.Horizontal2ValueSampling(bms, w / 80);

            //将采样的影像二值化
            var im = ImageOprationControl.ToBinary(bms, ImageOprationControl.GetThreshold(bms));

            //找左边竖线
            bms = ImageOprationControl.FindLeftLines(bms, im, 0.1f);

            //找横向分隔区
            bms = ImageOprationControl.FindTransverseSpace(bms, im, 0.2f);

            // 红色区域涂白，得到内容矩形阵图
            bms = ImageOprationControl.Getfield(bms);

            //找到每个矩形区
            var rs = ImageOprationControl.FindRectangle(bms);

            var folderName = DateTime.Now.ToString("yyyyMMddHHmmssffff");
            var path = ConfigOpration.SavePath + folderName + "\\";

            //将每个矩形区域保存为 tif 格式的图片
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            ImageOprationControl.SaveImageToTifForListImage(new Bitmap(binaryBm), rs, path);

            //OCR识别找药品
            for (int i = 1; i <= rs.Count; i++)
            {
                var _path = path + i + ".tif";
                var s = "";

                if (OCR.ImageToOCR(_path, ref s))
                {
                    var matchClass = new MatchMedicine();
                    //初始化数据
                    if (MatchMedicine.AllMedicinesFromDataBase == null)
                    {
                        MatchMedicine.AllMedicinesFromDataBase = matchClass.SelectMedicine();
                    }
                    string matchtMedicine = string.Empty;

                    //获取与字符串符合的药品
                    var hasMatch = MatchMedicine.SimilarMatching(s, ref matchtMedicine, ConfigOpration.RaForEncoding);

                    if (hasMatch)
                    {
                        var area = (rs[i - 1].X + contentRec.X) + "," + (rs[i - 1].Y + contentRec.Y) + "," +
                                   rs[i - 1].Width + "," + rs[i - 1].Height;
                        var m = new Medicine { Area = area, MedicineName = matchtMedicine };
                        ml.Add(m);
                    }
                }
            }

            return ml;
        }

        /// <summary>
        /// 得到药品区域影像，返回区域影像
        /// </summary>
        /// <param name="bm">影像</param>
        /// <param name="rectangle">药品区域</param>
        /// <param name="iw">影像宽</param>
        /// <param name="ih">影像高</param>
        /// <returns>药品区域影像</returns>
        private static Bitmap getMedicinesAreaBitmap(Bitmap bm, Rectangle rectangle, int iw, int ih)
        {
            var obm = new Bitmap(rectangle.Width, rectangle.Height);
            var data = bm.LockBits(new Rectangle(0, 0, iw, ih), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
            var dataObm = obm.LockBits(new Rectangle(0, 0, rectangle.Width, rectangle.Height),
                                       ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            //提取药品内容区
            unsafe
            {
                var p1 = (byte*)data.Scan0.ToPointer();
                var p2 = (byte*)dataObm.Scan0.ToPointer();

                for (int i = rectangle.X; i < rectangle.Width + rectangle.X; i++)
                {
                    for (int j = rectangle.Y; j < rectangle.Height + rectangle.Y; j++)
                    {
                        var ptr1 = p1 + j * data.Stride + i * 3;
                        var ptr2 = p2 + (j - rectangle.Y) * dataObm.Stride + (i - rectangle.X) * 3;
                        ptr2[2] = ptr1[2];
                        ptr2[1] = ptr1[1];
                        ptr2[0] = ptr1[0];
                    }
                }
            }

            bm.UnlockBits(data);
            obm.UnlockBits(dataObm);

            return obm;
        }

        /// <summary>
        /// 根据药品区域影像得到每个可能是药品的小区域
        /// </summary>
        /// <param name="obm">药品区域影像</param>
        /// <param name="contentRectangle">药品区域</param>
        /// <param name="rightOffset">投影分析右偏移量 </param>
        /// <param name="lineHeight">行高 </param>
        /// <returns>每个可能是药品的小区域</returns>
        private static List<List<Rectangle>> getEachMedicinesArea(Bitmap obm, Rectangle contentRectangle, int rightOffset, int lineHeight)
        {
            var gs = ImageOprationControl.ToBinary(obm, ImageOprationControl.GetThreshold(obm));
            var pgs = ImageOprationControl.HorizontalProjection(gs);
            var listRectangleRow = ImageOprationControl.GetRowArea(pgs, contentRectangle.Width, contentRectangle.Height, rightOffset, lineHeight);
            var listRectangles = ImageOprationControl.AnalysisColumn3(gs, listRectangleRow);
            return listRectangles;
        }

        #endregion
    }

    /// <summary>
    /// 直线描述类
    /// </summary>
    internal class Line
    {
        /// <summary>
        /// 公有坐标
        /// </summary>
        public int A { get; set; }

        /// <summary>
        /// 起点
        /// </summary>
        public int BMin { get; set; }

        /// <summary>
        /// 终点
        /// </summary>
        public int BMax { get; set; }
    }

    /// <summary>
    /// 药品描述类
    /// </summary>
    public class Medicine
    {
        /// <summary>
        /// 药品名称
        /// </summary>
        public string MedicineName = string.Empty;

        /// <summary>
        /// 药品所在区域
        /// </summary>
        public string Area = string.Empty;

        /// <summary>
        /// 相似度
        /// </summary>
        public int LenLike = 100;
    }
}
